/*
 * Copyright (c) 2022 Hong Jiahua
 * https://gitee.com/arrco/ersh_keyboard
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include "ersh_keyboard.h"
/************************************************************************/
/*                                                                      */
/************************************************************************/
#define ERSH_KEYBOARD_SUPPORT_CTRL_C_AND_CTRL_Z  1
#define ERSH_KEYBOARD_SUPPORT_CTRL_S_AND_CTRL_Q  1
#define ERSH_KEYBOARD_SUPPORT_CTRL_V             1
#define ERSH_KEYBOARD_SUPPORT_CTRL_M             1

#define ERSH_KEYBOARD_TURN_OFF_OUTPUT_PROCESSING 0

/************************************************************************/
/*                                                                      */
/************************************************************************/
#define ERSH_KEYBOARD_CODE_LEN_MAX   10

/************************************************************************/
/*                                                                      */
/************************************************************************/
typedef struct
{
    int id;
    int len;
    char escape_code[ERSH_KEYBOARD_CODE_LEN_MAX];
} keyboard_t;

static keyboard_t key_0_map[] = {
};

static keyboard_t key_1_map[] = {
    {KEY_CTRL_A,    1,  {1}},
    {KEY_CTRL_B,    1,  {2}},
    {KEY_CTRL_C,    1,  {3}},
    {KEY_CTRL_D,    1,  {4}},
    {KEY_CTRL_E,    1,  {5}},
    {KEY_CTRL_F,    1,  {6}},
    {KEY_CTRL_G,    1,  {7}},
    {KEY_CTRL_H,    1,  {8}},
    {KEY_CTRL_I,    1,  {9}},
    {KEY_CTRL_J,    1,  {10}},
    {KEY_CTRL_K,    1,  {11}},
    {KEY_CTRL_L,    1,  {12}},
    
    {KEY_ENTER,     1,  {13}},
    
    {KEY_CTRL_M,    1,  {13}},
    {KEY_CTRL_N,    1,  {14}},
    {KEY_CTRL_O,    1,  {15}},
    {KEY_CTRL_P,    1,  {16}},
    {KEY_CTRL_Q,    1,  {17}},
    {KEY_CTRL_R,    1,  {18}},
    {KEY_CTRL_S,    1,  {19}},
    {KEY_CTRL_T,    1,  {20}},
    {KEY_CTRL_U,    1,  {21}},
    {KEY_CTRL_V,    1,  {22}},
    {KEY_CTRL_W,    1,  {23}},
    {KEY_CTRL_X,    1,  {24}},
    {KEY_CTRL_Y,    1,  {25}},
    {KEY_CTRL_Z,    1,  {26}},
    
    {KEY_ESC,       1,  {27}},
    
    {KEY_CTRL_LEFT_SQUARE_BRACKET,  1,  {27}},
    {KEY_CTRL_BACKSLASH,            1,  {28}},
    {KEY_CTRL_RIGHT_SQUARE_BRACKET, 1,  {29}},
    {KEY_CTRL_SLASH,                1,  {31}},
};

static keyboard_t key_2_map[] = {
    {KEY_ALT_CTRL_SPACE,    2,  {27, 0}},
    
    {KEY_ALT_CTRL_A,        2,  {27, 1}},
    {KEY_ALT_CTRL_B,        2,  {27, 2}},
    {KEY_ALT_CTRL_C,        2,  {27, 3}},
    {KEY_ALT_CTRL_D,        2,  {27, 4}},
    {KEY_ALT_CTRL_E,        2,  {27, 5}},
    {KEY_ALT_CTRL_F,        2,  {27, 6}},
    {KEY_ALT_CTRL_G,        2,  {27, 7}},
    {KEY_ALT_CTRL_H,        2,  {27, 8}},
    {KEY_ALT_CTRL_I,        2,  {27, 9}},
    {KEY_ALT_CTRL_J,        2,  {27, 10}},
    {KEY_ALT_CTRL_K,        2,  {27, 11}},
    {KEY_ALT_CTRL_L,        2,  {27, 12}},
    {KEY_ALT_CTRL_M,        2,  {27, 13}},
    {KEY_ALT_CTRL_N,        2,  {27, 14}},
    {KEY_ALT_CTRL_O,        2,  {27, 15}},
    {KEY_ALT_CTRL_P,        2,  {27, 16}},
    {KEY_ALT_CTRL_Q,        2,  {27, 17}},
    {KEY_ALT_CTRL_R,        2,  {27, 18}},
    {KEY_ALT_CTRL_S,        2,  {27, 19}},
    {KEY_ALT_CTRL_T,        2,  {27, 20}},
    {KEY_ALT_CTRL_U,        2,  {27, 21}},
    {KEY_ALT_CTRL_V,        2,  {27, 22}},
    {KEY_ALT_CTRL_W,        2,  {27, 23}},
    {KEY_ALT_CTRL_X,        2,  {27, 24}},
    {KEY_ALT_CTRL_Y,        2,  {27, 25}},
    {KEY_ALT_CTRL_Z,        2,  {27, 26}},
    
    {KEY_ALT_SPACE,             2,  {27, 32}},
    {KEY_ALT_EXCLAMATION_MARK,  2,  {27, 33}},
    {KEY_ALT_DOUBLE_QUOTE,      2,  {27, 34}},
    {KEY_ALT_NUMBER,            2,  {27, 35}},
    {KEY_ALT_DOLLAR ,           2,  {27, 36}},
    {KEY_ALT_PERCENT,           2,  {27, 37}},
    {KEY_ALT_AMPERSAND,         2,  {27, 38}},
    {KEY_ALT_SINGLE_QUOTE,      2,  {27, 39}},
    {KEY_ALT_LEFT_PARENTHESIS,  2,  {27, 40}},
    {KEY_ALT_RIGHT_PARENTHESIS, 2,  {27, 41}},
    {KEY_ALT_ASTERISK,          2,  {27, 42}},
    {KEY_ALT_PLUS,              2,  {27, 43}},
    {KEY_ALT_COMMA,             2,  {27, 44}},
    {KEY_ALT_MINUS,             2,  {27, 45}},
    {KEY_ALT_PERIOD,            2,  {27, 46}},
    {KEY_ALT_SLASH,             2,  {27, 47}},
    {KEY_ALT_0,                 2,  {27, 48}},
    {KEY_ALT_1,                 2,  {27, 49}},
    {KEY_ALT_2,                 2,  {27, 50}},
    {KEY_ALT_3,                 2,  {27, 51}},
    {KEY_ALT_4,                 2,  {27, 52}},
    {KEY_ALT_5,                 2,  {27, 53}},
    {KEY_ALT_6,                 2,  {27, 54}},
    {KEY_ALT_7,                 2,  {27, 55}},
    {KEY_ALT_8,                 2,  {27, 56}},
    {KEY_ALT_9,                 2,  {27, 57}},
    {KEY_ALT_COLON,             2,  {27, 58}},
    {KEY_ALT_SEMICOLON,         2,  {27, 59}},
    {KEY_ALT_LESS_THAN,         2,  {27, 60}},
    {KEY_ALT_EQUALS,            2,  {27, 61}},
    {KEY_ALT_GREATER_THAN,      2,  {27, 62}},
    {KEY_ALT_QUESTION_MARK,     2,  {27, 63}},
    {KEY_ALT_AT_SIGN,           2,  {27, 64}},
    {KEY_ALT_A,                 2,  {27, 65}},
    {KEY_ALT_B,                 2,  {27, 66}},
    {KEY_ALT_C,                 2,  {27, 67}},
    {KEY_ALT_D,                 2,  {27, 68}},
    {KEY_ALT_E,                 2,  {27, 69}},
    {KEY_ALT_F,                 2,  {27, 70}},
    {KEY_ALT_G,                 2,  {27, 71}},
    {KEY_ALT_H,                 2,  {27, 72}},
    {KEY_ALT_I,                 2,  {27, 73}},
    {KEY_ALT_J,                 2,  {27, 74}},
    {KEY_ALT_K,                 2,  {27, 75}},
    {KEY_ALT_L,                 2,  {27, 76}},
    {KEY_ALT_M,                 2,  {27, 77}},
    {KEY_ALT_N,                 2,  {27, 78}},
    {KEY_ALT_O,                 2,  {27, 79}},
    {KEY_ALT_P,                 2,  {27, 80}},
    {KEY_ALT_Q,                 2,  {27, 81}},
    {KEY_ALT_R,                 2,  {27, 82}},
    {KEY_ALT_S,                 2,  {27, 83}},
    {KEY_ALT_T,                 2,  {27, 84}},
    {KEY_ALT_U,                 2,  {27, 85}},
    {KEY_ALT_V,                 2,  {27, 86}},
    {KEY_ALT_W,                 2,  {27, 87}},
    {KEY_ALT_X,                 2,  {27, 88}},
    {KEY_ALT_Y,                 2,  {27, 89}},
    {KEY_ALT_Z,                 2,  {27, 90}},
    
    {KEY_ALT_LEFT_SQUARE_BRACKET,   2,  {27, 91}},
    {KEY_ALT_BACKSLASH,             2,  {27, 92}},
    {KEY_ALT_RIGHT_SQUARE_BRACKET,  2,  {27, 93}},
    {KEY_ALT_CARET ,                2,  {27, 94}},
    {KEY_ALT_UNDERSCORE,            2,  {27, 95}},
    {KEY_ALT_GRAVE_ACCENT,          2,  {27, 96}},
    
    {KEY_ALT_LOWERCASE_A,       2,  {27, 97 }},
    {KEY_ALT_LOWERCASE_B,       2,  {27, 98 }},
    {KEY_ALT_LOWERCASE_C,       2,  {27, 99 }},
    {KEY_ALT_LOWERCASE_D,       2,  {27, 100}},
    {KEY_ALT_LOWERCASE_E,       2,  {27, 101}},
    {KEY_ALT_LOWERCASE_F,       2,  {27, 102}},
    {KEY_ALT_LOWERCASE_G,       2,  {27, 103}},
    {KEY_ALT_LOWERCASE_H,       2,  {27, 104}},
    {KEY_ALT_LOWERCASE_I,       2,  {27, 105}},
    {KEY_ALT_LOWERCASE_J,       2,  {27, 106}},
    {KEY_ALT_LOWERCASE_K,       2,  {27, 107}},
    {KEY_ALT_LOWERCASE_L,       2,  {27, 108}},
    {KEY_ALT_LOWERCASE_M,       2,  {27, 109}},
    {KEY_ALT_LOWERCASE_N,       2,  {27, 110}},
    {KEY_ALT_LOWERCASE_O,       2,  {27, 111}},
    {KEY_ALT_LOWERCASE_P,       2,  {27, 112}},
    {KEY_ALT_LOWERCASE_Q,       2,  {27, 113}},
    {KEY_ALT_LOWERCASE_R,       2,  {27, 114}},
    {KEY_ALT_LOWERCASE_S,       2,  {27, 115}},
    {KEY_ALT_LOWERCASE_T,       2,  {27, 116}},
    {KEY_ALT_LOWERCASE_U,       2,  {27, 117}},
    {KEY_ALT_LOWERCASE_V,       2,  {27, 118}},
    {KEY_ALT_LOWERCASE_W,       2,  {27, 119}},
    {KEY_ALT_LOWERCASE_X,       2,  {27, 120}},
    {KEY_ALT_LOWERCASE_Y,       2,  {27, 121}},
    {KEY_ALT_LOWERCASE_Z,       2,  {27, 122}},
    {KEY_ALT_OPENING_BRACE,     2,  {27, 123}},
    {KEY_ALT_VERTICAL_BAR,      2,  {27, 124}},
    {KEY_ALT_CLOSING_BRACE,     2,  {27, 125}},
    {KEY_ALT_TILDE,             2,  {27, 126}},
    {KEY_ALT_BACKSPACE,         2,  {27, 127}},
};

static keyboard_t key_3_map[] = {
    {KEY_END,   3, {27, 79, 70}},
    {KEY_HOME,  3, {27, 79, 72}},
    
    {KEY_F1,    3, {27, 79, 80}},
    {KEY_F2,    3, {27, 79, 81}},
    {KEY_F3,    3, {27, 79, 82}},
    {KEY_F4,    3, {27, 79, 83}},
    
    {KEY_UP,    3, {27, 91, 65}},
    {KEY_DOWN,  3, {27, 91, 66}},
    {KEY_RIGHT, 3, {27, 91, 67}},
    {KEY_LEFT,  3, {27, 91, 68}},
    
    {KEY_END,   3, {27, 91, 70}},
    {KEY_HOME,  3, {27, 91, 72}},
};

keyboard_t key_4_map[] = {
    {KEY_INSERT,    4, {27, 91, 50, 126}},
    {KEY_DELETE,    4, {27, 91, 51, 126}},
    {KEY_PAGE_UP,   4, {27, 91, 53, 126}},
    {KEY_PAGE_DOWN, 4, {27, 91, 54, 126}},
};

static keyboard_t key_5_map[] = {
    {KEY_F5,    5, {27, 91, 49, 53, 126}},
    {KEY_F6,    5, {27, 91, 49, 55, 126}},
    {KEY_F7,    5, {27, 91, 49, 56, 126}},
    {KEY_F8,    5, {27, 91, 49, 57, 126}},
    
    {KEY_F9,    5, {27, 91, 50, 48, 126}},
    {KEY_F10,   5, {27, 91, 50, 49, 126}},
    {KEY_F11,   5, {27, 91, 50, 51, 126}},
    {KEY_F12,   5, {27, 91, 50, 52, 126}},
};

static keyboard_t key_6_map[] = {
    {KEY_ALT_UP,                6,  {27, 91, 49, 59, 51, 65}},
    {KEY_ALT_DOWN,              6,  {27, 91, 49, 59, 51, 66}},
    {KEY_ALT_RIGHT,             6,  {27, 91, 49, 59, 51, 67}},
    {KEY_ALT_LEFT,              6,  {27, 91, 49, 59, 51, 68}},
    {KEY_ALT_END,               6,  {27, 91, 49, 59, 51, 70}},
    {KEY_ALT_HOME,              6,  {27, 91, 49, 59, 51, 72}},
    
    {KEY_ALT_F1,                6,  {27, 91, 49, 59, 51, 80}},
    {KEY_ALT_F2,                6,  {27, 91, 49, 59, 51, 81}},
    {KEY_ALT_F3,                6,  {27, 91, 49, 59, 51, 82}},
    {KEY_ALT_F4,                6,  {27, 91, 49, 59, 51, 83}},
    
    {KEY_ALT_CTRL_UP,           6,  {27, 91, 49, 59, 55, 65}},
    {KEY_ALT_CTRL_DOWN,         6,  {27, 91, 49, 59, 55, 66}},
    {KEY_ALT_CTRL_RIGHT,        6,  {27, 91, 49, 59, 55, 67}},
    {KEY_ALT_CTRL_LEFT,         6,  {27, 91, 49, 59, 55, 68}},
    
    {KEY_ALT_CTRL_END,          6,  {27, 91, 49, 59, 55, 70}},
    {KEY_ALT_CTRL_HOME,         6,  {27, 91, 49, 59, 55, 72}},
    
    {KEY_ALT_INSERT,            6,  {27, 91, 50, 59, 51, 126}},
    
    {KEY_ALT_CTRL_INSERT,       6,  {27, 91, 50, 59, 55, 126}},
    
    {KEY_ALT_DELETE,            6,  {27, 91, 51, 59, 51, 126}},
    {KEY_ALT_PAGE_UP,           6,  {27, 91, 53, 59, 51, 126}},
    
    {KEY_ALT_CTRL_PAGE_UP,      6,  {27, 91, 53, 59, 55, 126}},
    
    {KEY_ALT_PAGE_DOWN,         6,  {27, 91, 54, 59, 51, 126}},
    
    {KEY_ALT_CTRL_PAGE_DOWN,    6,  {27, 91, 54, 59, 55, 126}},
};

static keyboard_t key_7_map[] = {
    {KEY_ALT_F5,        7,  {27, 91, 49, 53, 59, 51, 126}},
    {KEY_ALT_F6,        7,  {27, 91, 49, 55, 59, 51, 126}},
    {KEY_ALT_F7,        7,  {27, 91, 49, 56, 59, 51, 126}},
    {KEY_ALT_F8,        7,  {27, 91, 49, 57, 59, 51, 126}},
    {KEY_ALT_F9,        7,  {27, 91, 50, 48, 59, 51, 126}},
    {KEY_ALT_F10,       7,  {27, 91, 50, 49, 59, 51, 126}},
    {KEY_ALT_F11,       7,  {27, 91, 50, 51, 59, 51, 126}},
    {KEY_ALT_F12,       7,  {27, 91, 50, 52, 59, 51, 126}},
};

typedef struct
{
    keyboard_t* map;
    int len;
} keyboard_map_t;

static keyboard_map_t key_map[] = {
    {key_0_map, sizeof(key_0_map)/sizeof(keyboard_t)},
    {key_1_map, sizeof(key_1_map)/sizeof(keyboard_t)},
    {key_2_map, sizeof(key_2_map)/sizeof(keyboard_t)},
    {key_3_map, sizeof(key_3_map)/sizeof(keyboard_t)},
    {key_4_map, sizeof(key_4_map)/sizeof(keyboard_t)},
    {key_5_map, sizeof(key_5_map)/sizeof(keyboard_t)},
    {key_6_map, sizeof(key_6_map)/sizeof(keyboard_t)},
    {key_7_map, sizeof(key_7_map)/sizeof(keyboard_t)},
};

/************************************************************************/
/*                                                                      */
/************************************************************************/
static struct termios orig_termios;

static void ersh_recover_mode(void)
{
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

static void ersh_modify_mode(void)
{
    int attr = 1;
    ioctl(STDIN_FILENO, FIONBIO, &attr);
    
    tcgetattr(STDIN_FILENO, &orig_termios);
    atexit(ersh_recover_mode);
    
    struct termios raw = orig_termios;
#if ERSH_KEYBOARD_SUPPORT_CTRL_S_AND_CTRL_Q
    raw.c_iflag &= ~(IXON);
#endif
#if ERSH_KEYBOARD_SUPPORT_CTRL_M
    raw.c_iflag &= ~(ICRNL);
#endif
    raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP);
#if ERSH_KEYBOARD_TURN_OFF_OUTPUT_PROCESSING
    raw.c_oflag &= ~(OPOST);
#endif
    raw.c_cflag |= (CS8);
#if ERSH_KEYBOARD_SUPPORT_CTRL_C_AND_CTRL_Z
    raw.c_lflag &= ~(ISIG);
#endif
#if ERSH_KEYBOARD_SUPPORT_CTRL_V
    raw.c_lflag &= ~(IEXTEN);
#endif
    raw.c_lflag &= ~(ECHO | ICANON);
    
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

/************************************************************************/
/*                                                                      */
/************************************************************************/
static struct termios test_termios;

static void ersh_recover_test_mode(void)
{
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &test_termios);
}

static void ersh_modify_test_mode(void)
{
    tcgetattr(STDIN_FILENO, &test_termios);
    atexit(ersh_recover_test_mode);
    
    struct termios raw = test_termios;
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    raw.c_oflag &= ~(OPOST);
    raw.c_cflag |= (CS8);
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

/**
  * @brief      键盘读取按键键值
  *
  * @return     无
  */
void ersh_keyboard_read_key(void)
{
    char c;
    ersh_modify_test_mode();
    while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
        if (iscntrl(c)) {
            printf("%d\r\n", c);
        } else {
            printf("%d ('%c')\r\n", c, c);
        }
    }
}
/************************************************************************/
/*                                                                      */
/************************************************************************/
typedef struct
{
    void (*callback)(int);
} ersh_keyboard_info_t;

static ersh_keyboard_info_t keyboard_info;

/**
  * @brief      键盘读取初始化
  * @param[in]  callback    接收按键事件的回调函数
  *
  * @return     无
  */
void ersh_keyboard_init(void (*callback)(int))
{
    ersh_modify_mode();
    keyboard_info.callback = callback;
}

/**
  * @brief      键盘读取轮询函数
  *
  * @return     无
  */
void ersh_keyboard_poll(void)
{
    char cbuf[ERSH_KEYBOARD_CODE_LEN_MAX];
    int len = 0;
    
    if(read(STDIN_FILENO, &cbuf[0], 1) > 0) {
        len++;
        while(read(STDIN_FILENO, &cbuf[len], 1) > 0 && len < ERSH_KEYBOARD_CODE_LEN_MAX) {
            len++;
        }
        
        if(len == 1) {
            if(keyboard_info.callback) {
                if(cbuf[0] < 32) {
                    for(int i = 0; i < key_map[len].len; i++) {
                        if(key_map[len].map[i].escape_code[0] == cbuf[0]) {
                            if(keyboard_info.callback)
                                keyboard_info.callback(key_map[len].map[i].id);
                            break;
                        }
                    }
                } else {
                    keyboard_info.callback(cbuf[0]);
                }
            }
        } else {
            for(int i = 0; i < key_map[len].len; i++) {
                int flag = 1;
                for(int j = 0; j < key_map[len].map[i].len; j++) {
                    if(key_map[len].map[i].escape_code[j] != cbuf[j]) {
                        flag = 0;
                        break;
                    }
                }
                if(flag && key_map[len].map[i].len) {
                    if(keyboard_info.callback)
                        keyboard_info.callback(key_map[len].map[i].id);
                    
                    break;
                }
            }
        }
    }
}
